package it.uniroma2.cloud.util;

import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
import static org.jclouds.compute.options.TemplateOptions.Builder.overrideLoginCredentials;
import static org.jclouds.compute.predicates.NodePredicates.all;
import static org.jclouds.compute.predicates.NodePredicates.runningInGroup;
import it.uniroma2.cloud.util.PropertiesMap.CloudProviderProperty;
import it.uniroma2.jcloudstests.Command;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.jclouds.compute.ComputeService;
import org.jclouds.compute.RunScriptOnNodesException;
import org.jclouds.compute.domain.ExecResponse;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.scriptbuilder.domain.Statement;

public abstract class AbstractProviderHelper implements ProviderHelper {

	public abstract LoginCredentials getLoginCredentials();

	public Iterable<? extends NodeMetadata> listRunningNodesInGroup(
			ComputeService computeService, String groupName) {
		return filter(computeService.listNodesDetailsMatching(all()),
		runningInGroup(groupName));
	}
	
	public Collection<String> getPrivateIpsInGroup(ComputeService computeService,
			String groupName) {
		Collection<String> ips = new ArrayList<String>();
		Iterable<? extends NodeMetadata> nodes = listRunningNodesInGroup(
				computeService, groupName);
		Iterator<? extends NodeMetadata> nodeIt = nodes.iterator();
		while (nodeIt.hasNext()) {
			NodeMetadata node = nodeIt.next();
			String ip = node.getPrivateAddresses().iterator().next();
			//System.out.println(ip);
			ips.add(ip);
		}
		return ips;
	}
	
	public Collection<String> getReachableIpsInGroup(ComputeService computeService,
			String groupName) {
		Collection<String> ips = new ArrayList<String>();
		Iterable<? extends NodeMetadata> nodes = listRunningNodesInGroup(
				computeService, groupName);
		Iterator<? extends NodeMetadata> nodeIt = nodes.iterator();
		while (nodeIt.hasNext()) {
			NodeMetadata node = nodeIt.next();
			String ip = getReachableIp(computeService, node);
			System.out.println(ip);
			ips.add(ip);
		}
		return ips;
	}
	
	public abstract String getReachableIp(ComputeService computeService,
			NodeMetadata node);

	public void runScriptOnGroup(ComputeService compute, String groupName,
			Statement command) throws RunScriptOnNodesException {
		// when you run commands, you can pass options to decide whether
		// to run it as root, supply or own credentials vs from cache,
		// and wrap in an init script vs directly invoke
		Map<? extends NodeMetadata, ExecResponse> execResponses = compute
				.runScriptOnNodesMatching(//
						runningInGroup(groupName), // predicate used to select
													// nodes
						command, // what you actually intend to run
						overrideLoginCredentials(getLoginCredentials()) // use
																		// the
																		// local
																		// user
																		// &
								// ssh key
								.runAsRoot(true)); // don't attempt to run as
													// root (sudo)

		for (Entry<? extends NodeMetadata, ExecResponse> response : execResponses
				.entrySet()) {
			System.out.printf(
					"<< node %s: %s%n",
					response.getKey().getId(),
					concat(response.getKey().getPrivateAddresses(), response
							.getKey().getPublicAddresses()));
			System.out.printf("<<     %s%n", response.getValue());
		}
	}

	public void runScriptOnInstance(ComputeService compute,
			String instanceName, Statement command)
			throws RunScriptOnNodesException {
		// when you run commands, you can pass options to decide whether
		// to run it as root, supply or own credentials vs from cache,
		// and wrap in an init script vs directly invoke
		ExecResponse response = compute.runScriptOnNode(//
				instanceName, // predicate used to select nodes
				command, // what you actually intend to run
				overrideLoginCredentials(getLoginCredentials()) // use
																// the
																// local
																// user
																// &
						// ssh key
						.runAsRoot(true)); // don't attempt to run as
											// root (sudo)

		System.out.printf("<< node %s: %n", instanceName);
		System.out.printf("<<     %s%n", response);
	}

//	public ChefContext buildChefContext() throws IOException {
//		PropertiesMap p = PropertiesMap.getInstance();
//
//		String endpoint = getChefURL();
//
//		String chefClientName = p.get(CloudProviderProperty.CHEF_CLIENT_NAME);
//		String pemFile = System.getProperty("user.home") + "/.chef/"
//				+ chefClientName + ".pem";
//		String clientCredential = Files.toString(new File(pemFile),
//				Charsets.UTF_8);
//
//		Properties chefConfig = new Properties();
//		chefConfig.put(ChefProperties.CHEF_VALIDATOR_NAME, chefClientName);
//		chefConfig.put(ChefProperties.CHEF_VALIDATOR_CREDENTIAL,
//				clientCredential);
//		chefConfig.put(ChefProperties.CHEF_VERSION, "10.18.2");
//
//		ChefContext chefContext = ContextBuilder.newBuilder("chef")
//				.endpoint(endpoint)
//				.credentials(chefClientName, clientCredential)
//				.modules(ImmutableSet.<Module> of(new SLF4JLoggingModule()))
//				.overrides(chefConfig).build(ChefContext.class);
//		return chefContext;
//	}

//	protected abstract String getChefURL();

	public abstract Template getTemplate(ComputeService computeService);

	public abstract void updateLoadBalancer(ComputeService computeService,
			Collection<String> ips);

	protected int[] getPortsToBeOpened() {
		return new int[] { 80, 443, 22, 8080, 8443 };
	}

	public void destroyNode(ComputeService computeService, NodeMetadata node) {
		computeService.destroyNode(node.getId());
	}

	public abstract NodeMetadata getNodeByReachableIp(ComputeService computeService,
			String groupName, String ip);

	public abstract String getLoadBalancerAddress(ComputeService computeService);

	public String getMonitoringAddress(ComputeService computeService) {
		String monId = PropertiesMap.getInstance().get(
				CloudProviderProperty.MONITORING_INSTANCE_ID);
		NodeMetadata monNodeMetadata = computeService.getNodeMetadata(monId);
		String ret = monNodeMetadata.getPublicAddresses().iterator().next();
		return ret;
	}
	
	public int howManyRunningWorkerNodes(ComputeService computeService){
		int ret = 0;
		Iterable<? extends NodeMetadata> nodes = listRunningNodesInGroup(
				computeService, Command.WORKERNODE);
		Iterator<? extends NodeMetadata> nodeIt = nodes.iterator();
		while (nodeIt.hasNext()) {
			nodeIt.next();
			ret++;
		}
		return ret;
	}

}
